﻿/* 
 * Copyright 2010 Justin Pealing
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using Minnow.Web.Native;

namespace Minnow.Web.FastCGI
{
    /// <summary>
    /// Handles an instance of a FastCGI application process.
    /// </summary>
    /// <remarks>
    /// To start a new instance of a FastCGI application process, use the static <see cref="StartProcess" /> method.
    /// </remarks>
    public class FastCGIProcess
    {
        private CreateProcess process;

        // TODO: Expose list of pending requests
        private List<int> usedRequestIds = new List<int>();
        private int currentId = 1;
        private List<Record> bufferedRecords = new List<Record>();

        /// <summary>
        /// Initialises a new instance of <see cref="FastCGIProcess" />.
        /// </summary>
        /// <param name="Process">The FastCGI child process.</param>
        /// <param name="ChildSocket">The listner socket owned by the child process.</param>
        /// <param name="Port">Port to connect to.</param>
        /// <exception cref="ArgumentNullException">Process is null.</exception>
        private FastCGIProcess(int Port, CreateProcess Process, Socket ChildSocket)
        {
            if (Process == null)
            {
                throw new ArgumentNullException("Process");
            }

            this.connection = new BufferedConnection(Port);
            this.process = Process;
        }

        /// <summary>
        /// Starts a new FastCGI application process.
        /// </summary>
        /// <param name="CommandLine">Command line to the FastCGI application.</param>
        /// <returns>Instance of <see cref="FastCGIProcess" /> that can be used to interact with the process.</returns>
        public static FastCGIProcess StartProcess(string CommandLine)
        {
            // TODO: Give the child processes handle to someone for safe-keeping
            Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            listener.Bind(new IPEndPoint(IPAddress.Any, 0));
            listener.Listen(1);

            SafeFileHandle childHandle;
            if (!NativeMethods.DuplicateHandle(
                NativeMethods.GetCurrentProcess(),
                new SafeFileHandle(listener.Handle, false),
                NativeMethods.GetCurrentProcess(),
                out childHandle,
                0,
                true,
                NativeMethods.DUPLICATE_SAME_ACCESS))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.CommandLine = CommandLine;
            startInfo.WorkingDirectory = Path.GetDirectoryName(CommandLine);
            startInfo.StandardInputHandle = childHandle;
            startInfo.RedirectStandardInput = false;
            startInfo.RedirectStandardOutput = false;
            startInfo.RedirectStandardError = false;
            startInfo.CreateNoWindow = true;
            CreateProcess process = CreateProcess.Start(startInfo);

            // TODO: What about if we decide to close the connection?
            // TODO: Initiating a connection to am existing process (by port number)
            int port = ((IPEndPoint)listener.LocalEndPoint).Port;
            return new FastCGIProcess(port, process, listener);
        }

        /// <summary>
        /// Initiates a new request to the FastCGI application.
        /// </summary>
        /// <param name="Role">The FastCGI role the application is expected to play.</param>
        /// <param name="KeepConnection">If false, the FastCGI application closes the connection after responding 
        /// to this request.  If true, the child FastCGI application does not close the connection after responding 
        /// to this request; the Web server retains responsibility for the connection.</param>
        /// <returns>New <see cref="FastCGIRequest" />.</returns>
        public FastCGIRequest StartRequest(Role Role, bool KeepConnection)
        {
            int requestId = this.GetRequestId();
            this.connection.Connection.BeginRequest(requestId, Role, KeepConnection);
            return new FastCGIRequest(this, this.connection, Role, requestId);
        }

        /// <summary>
        /// Gets the underlying FastCGI socket used to communicate with the FastCGI process.
        /// </summary>
        internal BufferedConnection Connection
        {
            get
            {
                return this.connection;
            }
        }
        private BufferedConnection connection;

        /// <summary>
        /// Obtains and reserves a request id.
        /// </summary>
        /// <returns>Request id.</returns>
        private int GetRequestId()
        {
            // TODO: This
            lock (this.currentIdLock)
            {
                return this.currentId++;
            }
        }
        private object currentIdLock = new object();

        /// <summary>
        /// Frees a request id.
        /// </summary>
        /// <param name="Id">Request id to free.</param>
        private void FreeRequestId(int Id)
        {
            // TODO: This
        }
    }
}
